<?php

class Jf_Controller_Dispatcher implements Jf_Controller_Dispatcher_Interface {

    protected $_router = null;

    protected $_request = null;

    protected $_response = null;

    protected $_plugins = array();

    protected static $_instance = null;

    protected $_auto_render = true;

    protected $_view = null;

    protected $_returnResponse = false;

    protected $_instantly_flush = false;

    protected $_default_module = '';

    protected $_default_controller = '';

    protected $_default_action = '';

    protected $_modulePath = null;

    public function autoRender ($flag) {
        if ( ! is_bool ($flag)) {
            return;
        } else {
            $this -> _auto_render = $flag;
        }
    }

    private function __clone () {}

    protected function __construct () {}

    public function disableView () {
        $this -> _auto_render = false;
    }

    public function isEnbleView () {
        return $this -> _auto_render;
    }

    public function dispatch (Jf_Controller_Request_Abstract $request = null) {
        if ($request == null) {
            $request = $this -> getRequest ();
        } else {
            $this -> setRequest ($request);
        }
        if ( ! ($request instanceof Jf_Controller_Request_Abstract)) {
            throw new Jf_Exception ('Expect a Jf_Controller_Request_Abstract instance');
        }
        if ($request instanceof Jf_Controller_Request_Http) {
            $response = new Jf_Controller_Response_Http ();
        } elseif ($request instanceof Jf_Controller_Request_Simple) {
            $response = new Jf_Controller_Response_Cli ();
        }
        $router = $this -> getRouter ();
        if ( ! $request -> isRouted ()) {
            foreach ($this -> _plugins as $plugin) {
                $plugin -> routeStartup ($request, $response);
            }
            try {
                // @todo here seems there is 2 type of routes
                $router -> route ($request);
            } catch (Exception $e) {
                if ($this -> throwException () == true) {
                    throw $e;
                }
            }
            $this -> _fixDefault ($request);
            foreach ($this -> _plugins as $plugin) {
                $plugin -> routeShutdown ($request, $response);
            }
        } else {
            $this -> _fixDefault ($request);
        }
        foreach ($this -> _plugins as $plugin) {
            $plugin -> dispatchLoopStartup ($request, $response);
        }
        $nested = Jf_Global::iniGet ('Jf.forward_limit');
        do {
            foreach ($this -> _plugins as $plugin) {
                $plugin -> preDispatch ($request, $response);
            }
            $this -> handle ($request, $response);
            $this -> _fixDefault ($request);
            foreach ($this -> _plugins as $plugin) {
                $plugin -> postDispatch ($request, $response);
            }
            $nested -- ;
        } while ( ! $request -> isDispatched () && $nested > 0);
        foreach ($this -> _plugins as $plugin) {
            $plugin -> dispatchLoopShutdown ($request, $response);
        }
        if ($nested == 0 &&  ! $request -> isDispatched ()) {
            throw new Jf_Exception ('The max dispatch nesting ' . Jf_Global::iniGet ('Jf.forward_limit') . ' was reached');
        }
        if ($this -> returnResponse () == false) {
            $response -> response ();
        }
        return $response;
    }

    private function handle (Jf_Controller_Request_Abstract $request, Jf_Controller_Response_Abstract $response) {
        $request -> setDispatched (true);
        $module = $request -> getModuleName ();
        $controllerName = $request -> getControllerName ();
        $className = $this -> getController ($module, $controllerName);
        if ( ! $className) {
            return false;
        }
        $controller = new $className ($request, $response);
        if ( ! $controller instanceof Jf_Controller_Action_Abstract) {
            throw new Jf_Exception ('Controller must be an instance of Jf_Controller_Abstract');
        }
        $action = $request -> getActionName ();
        $actionMethod = $action . 'Action';
        $controller -> $actionMethod ();
        if ($request -> isPost ()) {
            $this -> _auto_render = false;
        }
        if ($this -> _auto_render === true) {
            if ($this -> _instantly_flush == true) {
                $controller -> display ($action);
            } else {
                $ret = $controller -> render ($action);
                $response -> setBody ($ret);
            }
        }
    }

    private function getController ($module, $controller, $app = NULL) {
        $class = $module . '_' . 'Controller' . '_' . $controller;
        return $class;
    }

    private function _fixDefault (Jf_Controller_Request_Abstract $request) {
        $module = $request -> getModuleName ();
        if (empty ($module) ||  ! is_string ($module)) {
            $request -> setModuleName ($this -> _default_module);
        } else {
            $request -> setModuleName (ucfirst ($module));
        }
        $controller = $request -> getControllerName ();
        if (empty ($controller) ||  ! is_string ($controller)) {
            $request -> setControllerName ($this -> _default_controller);
        } else {
            $request -> setControllerName ($this -> _formatName ($controller));
        }
        $action = $request -> getActionName ();
        if (empty ($action) ||  ! is_string ($action)) {
            $request -> setActionName ($this -> _default_action);
        } else {
            // $request->setActionName(strtolower($action));
            $request -> setActionName ($action);
        }
    }

    private function _formatName ($unformatted) {
        $segments = explode ('_', $unformatted);
        if ($segments != null) {
            foreach ($segments as $key => $segment) {
                $segment = preg_replace ('/[^a-z0-9 ]/', '', strtolower ($segment));
                $segments[$key] = str_replace (' ', '', ucwords ($segment));
            }
            return implode ('_', $segments);
        }
    }

    public function enableView () {
        $this -> _auto_render = true;
    }

    public function flushInstantly ($flag) {
        if ( ! is_bool ($flag)) {
            return;
        } else {
            $this -> _instantly_flush = $flag;
        }
        return $this;
    }

    /**
     * returns the application
     * @return Jf_Application
     */
    public function getApplication () {
        return Jf_Application::app ();
    }

    public static function getInstance () {
        if (null === self::$_instance) {
            self::$_instance = new self ();
            self::$_instance -> setDefaultAction (Jf_Global::get ('default_action'));
            self::$_instance -> setDefaultController (Jf_Global::get ('default_controller'));
            self::$_instance -> setDefaultModule (ucfirst (Jf_Global::get ('default_module')));
            self::$_instance -> _router = new Jf_Controller_Router ();
        }
        return self::$_instance;
    }

    public function getRequest () {
        return $this -> _request;
    }

    public function getResponse () {
        return $this -> _response;
    }

    public function getRouter () {
        return $this -> _router;
    }

    public function registerPlugins (array $plugins) {
        foreach ($plugins as $pluginName => $pluginClass) {
            if (is_numeric ($pluginName)) {
                $pluginName = null;
            }
            if ( ! $pluginClass instanceof Jf_Controller_Plugin_Abstract) {
                throw new Jf_Exception ('plugin must instanceof Jf_Controller_Plugin_Abstract');
            }
            $this -> registerPlugin ($pluginClass, $pluginName);
        }
        return $this;
    }

    public function registerPlugin (Jf_Controller_Plugin_Abstract $plugin, $pluginName = null) {
        if ( ! $pluginName) {
            $this -> _plugins[] = $plugin;
        } else {
            if ($this -> hasPlugin ($pluginName)) {
                throw new Jf_Exception ("$pluginName has already been registered");
            } else {
                $this -> _plugins[$pluginName] = $plugin;
            }
        }
        return $this;
    }

    public function getPlugin ($pluginName) {
        if ( ! $this -> hasPlugin ($pluginName)) {
            throw new Jf_Exception ("$pluginName plugin never registered");
        }
        return $this -> _plugins[$pluginName];
    }

    public function unregisterPlugins (array $plugins) {
        foreach ($plugins as $plugin) {
            $this -> unregisterPlugin ($plugin);
        }
        return $this;
    }

    public function unregisterPlugin ($pluginName) {
        if ($this -> hasPlugin ($pluginName)) {
            unset ($this -> _plugins[$pluginName]);
        } else {
            throw new Jf_Exception ("$pluginName plugin never registered");
        }
        return $this;
    }

    public function hasPlugin ($pluginName) {
        if ( ! isset ($this -> _plugins[$pluginName])) {
            return false;
        }
        return true;
    }

    public function returnResponse ($flag = null) {
        if (true === $flag) {
            $this -> _returnResponse = true;
            return $this;
        } elseif (false === $flag) {
            $this -> _returnResponse = false;
            return $this;
        }
        return $this -> _returnResponse;
    }

    public function getDefaultAction () {
        return $this -> _default_action;
    }

    public function setDefaultAction ($action) {
        $this -> _default_action = (string) $action;
        return $this;
    }

    public function getDefaultController () {
        return $this -> _default_controller;
        ;
    }

    public function setDefaultController ($controller) {
        $this -> _default_controller = ucfirst ((string) $controller);
        return $this;
    }

    public function getDefaultModule () {
        return $this -> _default_module;
    }

    public function setDefaultModule ($module) {
        $this -> _default_module = $module;
        return $this;
    }

    public function setErrorHandler ($callback, $error_types = E_ALL) {
        set_error_handler ($callback, $error_types);
    }

    public function setRequest (Jf_Controller_Request_Abstract $request) {
        $this -> _request = $request;
        return $this;
    }

    /**
     * Enforce singleton; disallow serialization
     * @return void
     */
    private function __sleep () {}

    /**
     * Enforce singleton; disallow serialization
     * @return void
     */
    private function __wakeup () {}

    public function getModulePath ($module = null) {
        $module = isset ($module) ? $module : $this -> getModuleName ();
        if (empty ($this -> _modulePath[$module])) {
            $this -> setModulePath ($module);
        }
        return $this -> _modulePath[$module];
    }

    protected function setModulePath ($module) {
        $this -> _modulePath[$module] = Jf_Global::get ('directory') . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . $module;
        return $this;
    }

}
